
// Copyright (C) 2012 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//  
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//  
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;

namespace Derm.Xml.Collada
{
	/// <summary>
	/// Interface implemented by those object that can resolve specific COLLADA instance references.
	/// </summary>
	/// <typeparam name="T">
	/// A type implementing <see cref="IColladaInstantiable"/> interface.
	/// </typeparam>
	public interface IColladaResolutor<T> where T : IColladaInstantiable
	{
		/// <summary>
		/// Try to resolve the simple reference <paramref name="id"/>.
		/// </summary>
		/// <param name="id">
		/// A <see cref="System.String"/> that specify the URI fragment which identifies the reference.
		/// </param>
		/// <param name="document">
		/// A <see cref="ColladaDocument"/> used for resolving internal references.
		/// </param>
		/// <returns>
		/// It returns a <typeparamref name="T"/> value corresponding to <paramref name="id"/>. If no reference
		/// was found, it returns null.
		/// </returns>
		T Resolve(string id, ColladaDocument document);
	}

	/// <summary>
	/// 
	/// </summary>
	/// <typeparam name="T"></typeparam>
	public class ColladaInstanceLibrary<T> : ColladaXmlSerializable, IColladaResolutor<T> where T : IColladaInstantiable
	{
		#region Constructors

		/// <summary>
		/// Construct a ColladaInstanceLibrary.
		/// </summary>
		/// <param name="parent">
		/// The parent element containing this ColladaInstanceLibrary.
		/// </param>
		public ColladaInstanceLibrary(ColladaXmlSerializable parent) : base(parent)
		{
			
		}

		#endregion

		#region IColladaResolutor<T> Implementation

		/// <summary>
		/// Try to resolve the simple reference <paramref name="id"/>.
		/// </summary>
		/// <param name="id">
		/// A <see cref="System.String"/> that specify the URI fragment which identifies the reference.
		/// </param>
		/// <param name="document">
		/// A <see cref="ColladaDocument"/> used for resolving internal references.
		/// </param>
		/// <returns>
		/// It returns a <typeparamref name="T"/> value corresponding to <paramref name="id"/>. If no reference
		/// was found, it returns null.
		/// </returns>
		public T Resolve(string id, ColladaDocument document)
		{
			Type objType = GetType();
			List<object> colladaObjects = new List<object>();
			List<object> colladaCollections = new List<object>();

			if (id == null)
				throw new ArgumentNullException("id");
			if (document == null)
				throw new ArgumentNullException("document");

			// Convert identifier
			if (id.StartsWith("#") == true) {
				// XML fragment name: remove leading '#'
				id = id.Substring(1, id.Length - 1);
			}

			// Get fields value
			foreach (FieldInfo fieldInfo in objType.GetFields(BindingFlags.Public | BindingFlags.Instance)) {
				ColladaInstantiableAttribute instantiableAttribute = (ColladaInstantiableAttribute) Attribute.GetCustomAttribute(fieldInfo, typeof(ColladaInstantiableAttribute));
				ColladaInstantiableCollectionAttribute instantiableCollectionAttribute = (ColladaInstantiableCollectionAttribute) Attribute.GetCustomAttribute(fieldInfo, typeof(ColladaInstantiableCollectionAttribute));

				if ((instantiableAttribute != null) || (instantiableCollectionAttribute != null)) {
					object value = fieldInfo.GetValue(this);

					if (value != null) {
						if (instantiableAttribute != null)
							colladaObjects.Add(value);
						if (instantiableCollectionAttribute != null)
							colladaCollections.Add(value);
					}
				}
			}

			// Get property value
			foreach (PropertyInfo propertyInfo in objType.GetProperties(BindingFlags.Public | BindingFlags.Instance)) {
				if (propertyInfo.CanRead == false)
					continue;
				if (propertyInfo.GetIndexParameters().Length > 0)
					continue;

				ColladaInstantiableAttribute instantiableAttribute = (ColladaInstantiableAttribute) Attribute.GetCustomAttribute(propertyInfo, typeof(ColladaInstantiableAttribute));
				ColladaInstantiableCollectionAttribute instantiableCollectionAttribute = (ColladaInstantiableCollectionAttribute) Attribute.GetCustomAttribute(propertyInfo, typeof(ColladaInstantiableCollectionAttribute));

				if ((instantiableAttribute != null) || (instantiableCollectionAttribute != null)) {
					object value = propertyInfo.GetValue(this, null);

					if (value != null) {
						if (instantiableAttribute != null)
							colladaObjects.Add(value);
						if (instantiableCollectionAttribute != null)
							colladaCollections.Add(value);
					}
				}
			}

			// Resolve references
			foreach (object instantiableObject in colladaObjects) {
				IColladaInstantiable instantiable = (IColladaInstantiable) instantiableObject;

				if (instantiable.InstanceId == id) {
					return ((T) instantiable);
				}
			}

			foreach (object instantiableCollection in colladaCollections) {
				IEnumerable collection = (IEnumerable) instantiableCollection;

				foreach (object collectionItem in collection) {
					if ((collectionItem != null) && (collectionItem is IColladaInstantiable)) {
						IColladaInstantiable instantiable = (IColladaInstantiable) collectionItem;

						if (instantiable.InstanceId == id) {
							return ((T) instantiable);
						}
					}
				}
			}

			return (default(T));
		}

		#endregion
	}
}